staticVars
Variables Sandbox PanosP Revised

Using Static Variables

Problem
=======

Can we use variables with limited scope in awk?
How can we hide a variable inside a function
or any other program block?

Solution
========

In awk there is no need to declare or define
a variable before using it. Actually, all variables
are automatically come into existence the first time we use them.
Even if we wish to declare or define the variables
to use, there are no keywords such as _int_, _char_,
_float_ as in C, or Java, to do such a thing.
At first glance, that seems to be a disadvantage for awk,
because variables used in all functions, or other
program portions, e.g. BEGIN and END actions,
share a global name scope, that is every variable used
anywhere in the program is globaly known. Surely,
that's not a good thing for a language; unwanted globalness
is the origin of all evil in software.
Do we have a major problem then? The answer is:
Of course, no!

But how can we make a variable "local",
or, using some C or Java terminology,
"static" to some portion of a program?
The truth is that there is an elegant,
indirect way to declare variables
to be "local" in awk functions.
We know that every time we call
an awk function, all the values of the
parameters passed as arguments to
the function are copied to new variables
that come into existence when the function
is called and placed in new variables
named by the function definition.
When the function returns, all these
(temporary) variables are vanished
with just one exception: arrays.
Arrays are _passed by reference_,
but that is a topic to be discussed later.
Let's see some examples.

 function testVars(x, y,z) {
 	if ((x <= 0) || (y <= 0))
 		return(0)
 	z = sqrt((x * x) + (y * y))
 	if (z > (2 * x))
 		return(z)
 	return(x)
 }

The above function is defined to be
passed two (number)
variables and return either zero, or
another (number) value, based on some
tests done on the values of `x` and `y`
passed.
If called as

  testVars(-1, 10)

the function returns zero (because `x` value is
less than zero). If called as

  testVars(3, 4)

the function returns 3 (because the `z`
is calculated to  5 and that value is
less than two times `x`, which is 6,
thus returning the value of `x`
which is 3). It's clear that `x` and
`y` are "local" variables in the
`testVars` function, that is the names
`x` and `y` are known in the scope
of the `testVars` function and have nothing
to do with other variables named `x` or
`y` anywhere outside that function.
The `x` and `y` variables come into existence
when the function is called and receive as
values the corresponding values passed
as function arguments.
However, that's not true for the `z` variable.
The `z` variable is not declared as a 
function argument, so when assigning some
value to it, the `z` variable comes into
existence and is globaly known. More, if
the `z` variable was already a global variable
when we called the `testVars` function, then
any value already assigned to (global) `z` variable
is replaced by the expression calculated inside
`testVars`.

How can we avoid the globalness of the
`z` variable and localize the scope of `z`
inside the `testVars` function?
There is a simple solution to that kind
of problem, due to the freedom of the
arguments count to be passed in awk functions.
Actually, we can declare a function to accept
a specific number of arguments, but we
can call the function with less arguments than
those declared in the function's definition!
That's the key in creating "local" or "static"
variables to be known only in a function's
scope. Let the `testVars` function be declared as:
 
  function testVars(x, y,		z) {
  	if ((x <= 0) || (y <= 0))
  		return(0)
  	z = sqrt((x * x) + (y * y))
  	if (z > (2 * x))
  		return(z)
  	return(x)
  }

Declaring `z` as a function argument
the `z` variable become "static" in the `testVars`
function's scope. The above function definition
states that the `testVars` function accepts three
arguments (named as `x`, `y` and `z`), but we
use to call the function with only two of the
three declared arguments. That simple assumption
has a simple side effect of declaring local
`x`, `y` and `z` variables and the two values
passed when calling the function
are placed in `x` and `y` variables. We don't pass
a thrid variable, so the `z` variable is created
but not take any value (remains empty), but we can
use `z` variable in the function's code to store some values.

Discussion
==========

The method of creating local (static) function
variables just by declaring the desired variables
as (unpassed) function arguments is a very clever
idea of the awk creators. BTW, when passing arrays
as function arguments, we must have in mind that
the array elements are not copied; they just exist
with the same indices. If any new elements are
created during the function execution, or some elements
are deleted, then the array passed is affected, no
array elements are copied to static variables,
just the name of the array is to be new.
To give an example on passing arrays as
functon arguments we'll develop a function to
double all the values of the array passed.
We also add a new element indexed "sum" to
hold the doubled values' total, and return
the count of the array elements.

  function testArr(x,		i, n) {
  	delete x["sum"]
  	for (i in x) {
  		x[i] *= 2
  		x["sum"] += x[i]
  		n++
  	}
  	return(n)
  }

We define the function to accept _three_ values
as arguments, but `i`, and `n` will not actually
be passed when calling the function (that's the reason
to put some extra tabs before `i` and `n`, it's only
to emphasize the fact that `i` and `n` may not be passed,
but rather are variables to be used as static in `testArr`).
We first delete the "sum" indexed element (in case that
element already exists), and then iterate all elements
in the array passed using the (local) `i` variable
as the runing index. We double each element's value
and add the doubled value to the (recreated)
`x&#91;"sum"&#93;` element. We also increase the counter `n`,
also declared as local in `testArr`.

After visiting all the array elements, we
return the count of the elements visited.
However, there is a bug in the function.
We deleted the `x&#91;sum"&#93;` element in the
begining of the function, but we recreate the
element in the first loop pass.
Doing so, we'll visit that element in some
future loop pass and thus get erroneus results.
To fix that bug we declare another local
variable to hold the total, while the loop is on,
and create the "sum" indexed element after
visiting all the array elements:

  function testArr(x,		i, n, tot) {
  	delete x["sum"]
  	for (i in x) {
  		x[i] *= 2
  		tot += x[i]
  		n++
  	}
  	x["sum"] = tot
  	return(n)
  }

That's all folks!

Author
======

Panos Papadopoulos

